/*
 * BTNetworkAspect.aj
 * 
 *  author: Daniel Rudigier
 *    date: 08.12.2008
 * 
 * 
 */
package at.syncme.aspects.network;

import java.io.IOException;

import at.syncme.aspects.GameAspect;
import at.syncme.framework.Event;
import at.syncme.framework.Network;
import at.syncme.framework.Game;
import at.syncme.framework.midp.BTClientNetworkManager;
import at.syncme.framework.midp.BTServerNetworkManager;
import at.syncme.framework.midp.ClientNetworkManager;
import at.syncme.framework.midp.ServerNetworkManager;

/**
 * bluetooth networking as an aspect for client server games. it uses the bt
 * helper classes for handling the bluetooth technology. it will update derived 
 * classes from csgame:
 * <ul>
 *  <li>connectionUrls: discovered services</li>
 *  <li>connected: connection state</li> 
 * </ul>
 * 
 * @author Daniel Rudigier
 */
public class BTNetwork extends Network {
    private static BTNetwork instance;

    private String service;

    private String uuid;

    private ServerNetworkManager sm;

    private ClientNetworkManager cm;

    private Game game;

    /**
     * get the bluetooth network handler
     * 
     * @param service
     * @param uuid
     * @return instance
     */
    public static BTNetwork getInstance(String service, String uuid) {
        if (instance == null) {
            instance = new BTNetwork(service, uuid);
        }
        return instance;
    }

    /**
     * constructor
     */
    private BTNetwork(String service, String uuid) {
        this.service = service;
        this.uuid = uuid;
    }

    /**
     * @param game
     *            to set
     */
    protected void setGame(Game game) {
        this.game = game;
    }

    /**
     * a setup event
     * 
     * @param e
     */
    protected void setupEventReceived(Event e) {
        try {
            if (e.getId() == Event.SETUP_SERVER) {
                System.out.println("[bt] listening");
                disconnectClient();

                this.sm = BTServerNetworkManager.getManager(service, uuid);
                this.sm.listen();
            } else if (e.getId() == Event.SETUP_CLIENT) {
                System.out.println("[bt] discovering");
                disconnectServer();

                this.cm = BTClientNetworkManager.getManager(service, uuid);
                this.game.setConnectionUrls(this.cm.discover(false));
            } else if (e.getId() == Event.SETUP_EXIT) {
                System.out.println("[bt] cleanup");
                disconnectClient();
                disconnectServer();
            } else if (e.getId() == Event.SETUP_CONNECT) {
                if (e.getMessage() != null) {
                    try {
                        Integer index = (Integer) e.getMessage();
                        connect(index.intValue());
                    } catch (ClassCastException ex) {
                        System.out.println("[bt] connect expects an integer object");
                    }
                }
                // TODO: decode the serviceId from the event object
                
            } else if (e.getId() == Event.SETUP_DISCONNECT) {
                System.out.println("[bt] disconnect");
                disconnectClient();
            } else {
                // nop
            }
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }

    private void disconnectClient() {
        if (this.cm != null && this.cm.isConnected()) {
            this.cm.disconnect();
            this.game.setConnected(false);
        }
    }

    private void disconnectServer() {
        if (this.sm != null && this.sm.isListening()) {
            this.sm.disconnect();
        }
    }

    /**
     * client: establish a new connection
     */
    private void connect(int serviceId) {
        try {
            if (this.cm != null) {
                if (this.cm.isConnected()) {
                    disconnectClient();
                }

                this.cm.connect(serviceId);
                this.cm.startReceival();
                this.game.setConnected(true);
            }
        } catch (final IllegalArgumentException ex) {
            System.out.println("invalid service id provided.");
            this.game.setConnected(false);
        } catch (final IOException ex) {
            System.out.println("could not connect");
            ex.printStackTrace();
            this.game.setConnected(false);
        }
    }

    /**
     * override
     * 
     * @see at.syncme.framework.Network#sendToAllNodes(at.syncme.framework.Event)
     */
    public void sendToAllNodes(Event e) {
        System.out.println("network: broadcast " + e);
        try {
            if (this.sm != null) {
                this.sm.send(e);
            } else if (this.cm != null) {
                this.cm.send(e);
            } else {
                // don't care
            }
        } catch (IOException ex) {
            // don't care here?
        }
    }

    /**
     * override
     * 
     * @see at.syncme.framework.Network#sendToNode(int,
     *      at.syncme.framework.Event)
     */
    public void sendToNode(short nodeId, Event e) {
        try {
            if (this.sm != null) {
                this.sm.sendUnicast(nodeId, e);
            } else if (this.cm != null) {
                // the client is 
                this.cm.send(e);
            }
        } catch (IOException ex) {
            ex.printStackTrace();
            // don't care 
        }
    }

    /**
     * some hooks of the underlying blue-tooth helper classes
     * 
     * @author Daniel Rudigier
     */
    private static aspect BTNetworkAspect extends GameAspect {
        pointcut setupEvent(Event e): 
            execution (void at.syncme.framework.Game.setupEvent(Event)) && args(e);

        pointcut clientConnected(short nodeId): 
            execution (at.syncme.framework.midp.BTServerNetworkManager.Receiver.new(..))
                && args(..,nodeId);
        
        /*
         * cannot use this handler for clientDisconnected
         */
        pointcut ioException(IOException e):
            handler (IOException+) 
                && within(at.syncme.framework.midp.BTServerNetworkManager.Receiver)
                && args(e);
        
        pointcut clientDisconnected(short nodeId):
            call (void at.syncme.framework.midp.BTServerNetworkManager.Receiver.cleanup(..))
                && args(nodeId);
         

        /**
         * after a new game has been created
         */
        after() returning(Game g): gameInit() {
            System.out.println("[bt] active");
            instance.setGame(g);
        }

        /**
         * handle a game mode events (either server, client, connect or nothing)
         */
        after(Event e): setupEvent(e) {
            instance.setupEventReceived(e);
        }

        /**
         * server: handle incoming client connections ( lead to a new player
         * event )
         */
        after(short nodeId): clientConnected(nodeId) {
            System.out.println("[bt] client challenge: " + nodeId);
            instance.getAlgorithm().eventReceived(
                    Event.createFromNetwork(Event.PLAYER_CHALLANGE, nodeId));
        }
        
        /**
         * server: handle disconnects
         * @param e
         */
        before(short nodeId): clientDisconnected(nodeId) {
            System.out.println("[bt] client disconnected: " + nodeId);
            instance.getAlgorithm().eventReceived(
                    Event.createFromNetwork(Event.PLAYER_DISCONNECTED, nodeId));
        }
        
        /**
         * when an event was read from network
         */
        after() returning (Event e): eventReceived() {
            instance.getAlgorithm().eventReceived(e);
        }

    }
}
